﻿using System.Collections.Concurrent;
using System.ComponentModel;
using System.Text;

namespace System.NETool;

/// <summary>
/// 日志级别（与NLog一致）
/// </summary>
public enum LogLevel
{
    /// <summary>
    /// 详细信息：通常用于内部调试目的。包含最详细的跟踪信息。
    /// </summary>
    [Description("Trace")]
    Trace = 0,

    /// <summary>
    /// 调试信息：通常用于记录程序运行时的详细流程信息。
    /// </summary>
    [Description("Debug")]
    Debug = 1,

    /// <summary>
    /// 一般信息：通常用于记录应用程序的运行流程或业务逻辑处理情况，不包含敏感信息。
    /// </summary>
    [Description("Info")]
    Info = 2,

    /// <summary>
    /// 警告信息：表示可能发生的问题，但不一定影响应用程序的运行。
    /// </summary>
    [Description("Warn")]
    Warn = 3,

    /// <summary>
    /// 错误信息：表示应用程序的某个部分发生了错误，但应用程序仍能继续运行。
    /// </summary>
    [Description("Error")]
    Error = 4,

    /// <summary>
    /// 致命错误：表示应用程序遇到了无法恢复的错误，通常会导致应用程序的崩溃或停止运行。
    /// </summary>
    [Description("Fatal")]
    Fatal = 5,

    /// <summary>
    /// 关闭日志记录：这是一个特殊的级别，用于关闭日志记录。
    /// </summary>
    [Description("Off")]
    Off = 6
}

/// <summary>
/// 日志记录接口
/// </summary>
public interface ILogger
{
    /// <summary>
    /// 日志级别
    /// </summary>
    LogLevel Level { get; set; }

    /// <summary>
    /// 详细信息：通常用于内部调试目的。包含最详细的跟踪信息。
    /// </summary>
    void Trace(params object[] args);

    /// <summary>
    /// 调试信息：通常用于记录程序运行时的详细流程信息。
    /// </summary>
    void Debug(params object[] args);

    /// <summary>
    /// 一般信息：通常用于记录应用程序的运行流程或业务逻辑处理情况，不包含敏感信息。
    /// </summary>
    void Info(params object[] args);

    /// <summary>
    /// 警告信息：表示可能发生的问题，但不一定影响应用程序的运行。
    /// </summary>
    void Warn(params object[] args);

    /// <summary>
    /// 错误信息：表示应用程序的某个部分发生了错误，但应用程序仍能继续运行。
    /// </summary>
    void Error(params object[] args);

    /// <summary>
    /// 致命错误：表示应用程序遇到了无法恢复的错误，通常会导致应用程序的崩溃或停止运行。
    /// </summary>
    void Fatal(params object[] args);

    /// <summary>
    /// 生成日志字符串
    /// </summary>
    /// <param name="level">日志级别</param>
    /// <param name="args">日志内容</param>
    /// <returns>生成的日志字符串</returns>
    string GenerateLog(LogLevel level, params object[] args);

    /// <summary>
    /// 写入日志
    /// </summary>
    /// <param name="log">日志内容</param>
    void WriteLog(string log);
}

/// <summary>
/// 基础日志记录类
/// </summary>
public abstract class BaseLogger : ILogger
{
    /// <summary>
    /// 日志级别
    /// </summary>
    public LogLevel Level { get; set; } = LogLevel.Off;

    /// <summary>
    /// 详细信息：通常用于内部调试目的。包含最详细的跟踪信息。
    /// </summary>
    public void Trace(params object[] args)
    {
        if (LogLevel.Trace >= Level)
            WriteLog(LogLevel.Trace, GenerateLog(LogLevel.Trace, args));
    }

    /// <summary>
    /// 调试信息：通常用于记录程序运行时的详细流程信息。
    /// </summary>
    public void Debug(params object[] args)
    {
        if (LogLevel.Debug >= Level)
            WriteLog(LogLevel.Debug, GenerateLog(LogLevel.Debug, args));
    }

    /// <summary>
    /// 一般信息：通常用于记录应用程序的运行流程或业务逻辑处理情况，不包含敏感信息。
    /// </summary>
    public void Info(params object[] args)
    {
        if (LogLevel.Info >= Level)
            WriteLog(LogLevel.Info, GenerateLog(LogLevel.Info, args));
    }

    /// <summary>
    /// 警告信息：表示可能发生的问题，但不一定影响应用程序的运行。
    /// </summary>
    public void Warn(params object[] args)
    {
        if (LogLevel.Warn >= Level)
            WriteLog(LogLevel.Warn, GenerateLog(LogLevel.Warn, args));
    }

    /// <summary>
    /// 错误信息：表示应用程序的某个部分发生了错误，但应用程序仍能继续运行。
    /// </summary>
    public void Error(params object[] args)
    {
        if (LogLevel.Error >= Level)
            WriteLog(LogLevel.Error, GenerateLog(LogLevel.Error, args));
    }

    /// <summary>
    /// 致命错误：表示应用程序遇到了无法恢复的错误，通常会导致应用程序的崩溃或停止运行。
    /// </summary>
    public void Fatal(params object[] args)
    {
        if (LogLevel.Fatal >= Level)
            WriteLog(LogLevel.Fatal, GenerateLog(LogLevel.Fatal, args));
    }

    /// <summary>
    /// 生成日志字符串
    /// </summary>
    /// <param name="level">日志级别</param>
    /// <param name="args">日志内容</param>
    /// <returns>生成的日志字符串</returns>
    public string GenerateLog(LogLevel level, params object[] args)
    {
        return $"{DateTime.Now.DateTimeExString()} | {level.Description()} | {string.Join(",", args)}";
    }

    /// <summary>
    /// 写入日志
    /// </summary>
    /// <param name="log">日志内容</param>
    public abstract void WriteLog(string log);

    /// <summary>
    /// 写入日志
    /// </summary>
    /// <param name="level">日志级别</param>
    /// <param name="log">日志内容</param>
    public virtual void WriteLog(LogLevel level, string log) => WriteLog(log);
}

/// <summary>
/// 控制台日志记录
/// </summary>
public class ConsoleLogger : BaseLogger
{
    /// <summary>
    /// 构造函数
    /// </summary>
    public ConsoleLogger(LogLevel level = LogLevel.Info)
    {
        Level = level;
    }

    /// <summary>
    /// 默认实例
    /// </summary>
    public static ConsoleLogger Instance = new();

    /// <summary>
    /// 写入日志
    /// </summary>
    /// <param name="log">日志内容</param>
    public override void WriteLog(string log)
    {
        Console.WriteLine(log);
    }

#if NET8_0
    private static readonly object Lock = new();
#elif NET9_0_OR_GREATER
    private static readonly Lock Lock = new();
#endif

    /// <summary>
    /// 写入日志
    /// </summary>
    /// <param name="level">日志级别</param>
    /// <param name="log">日志内容</param>
    public override void WriteLog(LogLevel level, string log)
    {
        lock (Lock)
        {
            switch (level)
            {
                case LogLevel.Debug:
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    break;
                case LogLevel.Info:
                    Console.ForegroundColor = ConsoleColor.Green;
                    break;
                case LogLevel.Warn:
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    break;
                case LogLevel.Error:
                    Console.ForegroundColor = ConsoleColor.Red;
                    break;
                case LogLevel.Fatal:
                    Console.ForegroundColor = ConsoleColor.Magenta;
                    break;
                default:
                    Console.ForegroundColor = ConsoleColor.Gray;
                    break;
            }

            base.WriteLog(level, log);
            Console.ResetColor();
        }
    }
}

/// <summary>
/// 日志文件记录类
/// </summary>
public class FileLogger : BaseLogger, IDisposable
{
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="savePeriod">保存间隔，大于0时使用缓存保存，单位：ms</param>
    public FileLogger(int savePeriod = 100) : this("Log", savePeriod) { }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="level">日志级别</param>
    /// <param name="savePeriod">保存间隔，大于0时使用缓存保存，单位：ms</param>
    public FileLogger(LogLevel level, int savePeriod = 100) : this("Log", string.Empty, level, savePeriod) { }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="name">日志文件名称</param>
    /// <param name="savePeriod">保存间隔，大于0时使用缓存保存，单位：ms</param>
    public FileLogger(string name, int savePeriod = 100) : this(name, string.Empty, LogLevel.Info, savePeriod) { }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="name">日志文件名称</param>
    /// <param name="level">日志级别</param>
    /// <param name="savePeriod">保存间隔，大于0时使用缓存保存，单位：ms</param>
    public FileLogger(string name, LogLevel level, int savePeriod = 100) : this(name, string.Empty, level, savePeriod) { }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="name">日志文件名称</param>
    /// <param name="dir">日志文件夹名称</param>
    /// <param name="level">日志级别</param>
    /// <param name="savePeriod">保存间隔，大于0时使用缓存保存，单位：ms</param>
    public FileLogger(string name, string dir, LogLevel level = LogLevel.Info, int savePeriod = 100)
    {
        _queue = new ConcurrentQueue<string>();

        Level = level;
        MaxFileSize = 4 * 1000 * 1000; // 4MB
        FileNamePrefix = name;
        FilePath = Path.Combine(Dir.CurrentDir(), "Logs").DealPath();
        if (!string.IsNullOrEmpty(dir)) FilePath = Path.Combine(FilePath, dir).DealPath();
        Dir.CreateDir(FilePath);
        StartThread(savePeriod);
    }

    private bool _useCache;
    private readonly ConcurrentQueue<string> _queue;
    public string FileNamePrefix;
    private Timer _timer;

    /// <summary>
    /// 获取或设置日志文件的路径
    /// </summary>
    public string FilePath { get; }

    /// <summary>
    /// 获取或设置定义日志文件大小
    /// </summary>
    public int MaxFileSize { get; set; }

    /// <summary>
    /// 开启线程
    /// </summary>
    private void StartThread(int period)
    {
        _useCache = period > 0;
        if (!_useCache) return;

        _timer = new Timer(TimerCallback, null, 0, period);
    }

    private bool _isWriting;
    private void TimerCallback(object o)
    {
        if (IsDisposed) return;
        if (_timer == null) return;
        if (_isWriting) return;
        _isWriting = true;
        using var sb = new ValueStringBuilder(1024);
        string date = "";
        string tomorrowLog = "";
        while (!_queue.IsEmpty)
        {
            if (_queue.TryDequeue(out var log))
            {
                if (date == "")
                {
                    date = log.Left(DateTimeEx.DateFormat.Length);
                }
                else
                {
                    if (log.Left(DateTimeEx.DateFormat.Length) != date)
                    {
                        tomorrowLog = log;
                        break;
                    }
                }

                sb.AppendLine(log);
            }
        }

        string filename = "";
        try
        {
            if (sb.Length > 0)
            {
                filename = GetLogFileName(date, DateTimeEx.DateFormat);
                var sw = new StreamWriter(filename, true, Encoding.UTF8);
                sw.Write(sb.ToString());
                sw.Flush();
                sw.Close();

                WriteAtOnce(tomorrowLog);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Write log error: " + filename + ", " + ex.Message);
        }
        finally
        {
            _isWriting = false;
        }
    }

    /// <summary>
    /// 是否释放标志
    /// </summary>
    private volatile bool _disposed;

    /// <summary>
    /// 保证重复释放资源时，不重复释放
    /// </summary>
    public bool IsDisposed => _disposed;

    /// <summary>
    /// 判断是否已经被释放，如果是，则抛出异常。
    /// </summary>
    /// <exception cref="ObjectDisposedException"></exception>
    public void ThrowIfDisposed()
    {
        if (IsDisposed) throw new ObjectDisposedException(this.GetType().FullName);
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    public void Dispose()
    {
        // 必须为true
        Dispose(true);

        // 通知GC垃圾回收机制不再调用终结器（析构器）
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// 释放资源
    /// </summary>
    /// <param name="disposing">是否释放托管资源</param>
    protected virtual void Dispose(bool disposing)
    {
        if (IsDisposed) return;

        if (disposing)
        {
            TimerCallback(null);
            _timer?.Change(-1, -1);
            _timer?.Dispose();
        }

        // 让类知道自己已经被释放
        _disposed = true;
    }

    private int _lastFileIndex = 1;
    private string _lastFilePath = "";

    /// <summary>
    /// 获取日志文件名
    /// </summary>
    /// <param name="log">日志内容</param>
    /// <param name="format">日期格式</param>
    /// <returns>日志文件名</returns>
    private string GetLogFileName(string log, string format)
    {
        if (!log.StartsWith("20")) return "";
        DateTime dt = log.Left(format.Length).ToDateTime(format);
        string filepath = Path.Combine(FilePath, dt.YearMonthString(false)).DealPath();
        Dir.CreateDir(filepath);
        if (!filepath.Equals(_lastFilePath))
        {
            _lastFilePath = filepath;
            _lastFileIndex = 1;
        }

        string fileName = string.Concat(filepath, dt.ToString("yyyyMMdd"), ".", _lastFileIndex.ToString("D2"), ".", FileNamePrefix, ".txt");
        FileInfo fi = new(fileName);
        while (true)
        {
            if (!fi.Exists || fi.Length < MaxFileSize)
            {
                break;
            }

            _lastFileIndex++;
            fileName = string.Concat(filepath, dt.ToString("yyyyMMdd"), ".", _lastFileIndex.ToString("D2"), ".", FileNamePrefix, ".txt");
            fi = new(fileName);
        }

        return fileName;
    }

    /// <summary>
    /// 写入日志
    /// </summary>
    /// <param name="log">日志内容</param>
    public override void WriteLog(string log)
    {
        if (_useCache)
        {
            _queue.Enqueue(log);
        }
        else
        {
            WriteAtOnce(log);
        }
    }

    /// <summary>
    /// 立即写入日志
    /// </summary>
    /// <param name="log">日志内容</param>
    private void WriteAtOnce(string log)
    {
        if (IsDisposed) return;
        if (string.IsNullOrEmpty(log)) return;
        string filename = "";
        try
        {
            filename = GetLogFileName(log, DateTimeEx.DateFormat);
            using var sw = File.AppendText(filename);
            sw.WriteLine(log);
            sw.Flush();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Write log error: " + filename + ", " + ex.Message);
        }
    }
}
